home *** CD-ROM | disk | FTP | other *** search
- // vim: tabstop=2 softtabstop=2 shiftwidth=2 expandtab
- //
- // BEGIN FLOCK GPL
- //
- // Copyright Flock Inc. 2005-2007
- // http://flock.com
- //
- // This file may be used under the terms of of the
- // GNU General Public License Version 2 or later (the "GPL"),
- // http://www.gnu.org/licenses/gpl.html
- //
- // Software distributed under the License is distributed on an "AS IS" basis,
- // WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
- // for the specific language governing rights and limitations under the
- // License.
- //
- // END FLOCK GPL
- //
-
- const OPML_CONTRACTID = '@flock.com/opml-service;1';
- const OPML_CLASSID = Components.ID('{a3948322-0bb8-413c-8c6d-b0959d6d5bad}');
- const OPML_CLASSNAME = 'Flock OPML Service';
-
- const MARK_READ_CONTRACTID = '@flock.com/opml-subscribe-listener-mark-read;1';
- const MARK_READ_CLASSID = Components.ID('{4cb09bdd-cca3-4f8d-86b4-d4b315c42b45}');
- const MARK_READ_CLASSNAME = 'Flock OPML Mark Read on Subscribe Listener';
-
-
- const Cc = Components.classes;
- const Ci = Components.interfaces;
- const Cr = Components.results;
-
- const XML_HEADER = '<?xml version="1.0" encoding="UTF-8"?>\n'
-
- const MAX_OPML_DEPTH = 20;
-
- /* from nspr's prio.h */
- const PR_RDONLY = 0x01;
- const PR_WRONLY = 0x02;
- const PR_RDWR = 0x04;
- const PR_CREATE_FILE = 0x08;
- const PR_APPEND = 0x10;
- const PR_TRUNCATE = 0x20;
- const PR_SYNC = 0x40;
- const PR_EXCL = 0x80;
-
-
- function writeXML(xml, file) {
- var xmlStr = XML_HEADER + xml.toXMLString();
-
- if (file.exists())
- file.remove(false);
-
- file.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, 0644);
-
- var ostream = Cc['@mozilla.org/network/file-output-stream;1']
- .createInstance(Ci.nsIFileOutputStream);
-
- ostream.init(file, PR_WRONLY | PR_CREATE_FILE, 0644, 0);
-
- var converter = Cc['@mozilla.org/intl/scriptableunicodeconverter']
- .createInstance(Ci.nsIScriptableUnicodeConverter);
- converter.charset = 'UTF-8';
-
- var convdata = converter.ConvertFromUnicode(xmlStr) + converter.Finish();
-
- ostream.write(convdata, convdata.length);
-
- ostream.flush();
- ostream.close();
- }
-
-
- function OpmlRequest(url, folder, flatRoot, listener, ctxt, logger) {
- this._url = url;
- this._folder = folder;
- this._flatRoot = flatRoot;
- this._listener = listener;
- this._ctxt = ctxt;
- this._logger = logger;
-
- var ios = Cc['@mozilla.org/network/io-service;1']
- .getService(Ci.nsIIOService);
- this._channel = ios.newChannelFromURI(url, null, null);
- }
-
- OpmlRequest.prototype = {
- get: function OR_get() {
- this._channel.asyncOpen(this, null);
- },
-
- onStartRequest: function OR_onStartRequest(request, context) {
- this._xml = '';
- },
- onStopRequest: function OR_onStopRequest(request, context, status) {
- if (Components.isSuccessCode(status))
- this._subscribe();
- else if (this._listener)
- this._listener.onLoadError(this._ctxt, null);
- },
- onDataAvailable: function OR_onDataAvailable(request, context, inputStream,
- sourceOffset, count) {
- var sstream = Cc['@mozilla.org/scriptableinputstream;1']
- .createInstance(Ci.nsIScriptableInputStream);
- sstream.init(inputStream);
-
- this._xml += sstream.read(count);
- },
-
- _sendLoadError: function OR__sendLoadError(errorCode) {
- if (this._listener) {
- var error = Cc['@flock.com/error;1'].createInstance(Ci.flockIError);
- error.errorCode = errorCode;
- this._listener.onLoadError(this._ctxt, error);
- }
- },
-
- _subscribe: function OR__subscribe() {
- var url = this._url.spec;
- this._logger.info('got OPML data from ' + url);
-
- try {
- var xmlData = new XML(this._xml.replace(/<\?xml[^?]*\?>/, ''));
- }
- catch (e) {
- this._logger.error('Invalid XML at ' + url);
- this._sendLoadError(Ci.flockIError.OPML_INVALID_XML);
- return;
- }
-
- var body = xmlData.body;
-
- if (xmlData.name() != 'opml' || !body) {
- this._logger.error('No OPML data at ' + url);
- this._sendLoadError(Ci.flockIError.OPML_NO_DATA);
- return;
- }
-
- var root;
- if (!this._flatRoot) {
- var title = xmlData.head.title;
- if (title && title.toString().length > 0)
- root = [xmlData.head.title.toString()];
- else
- root = ['Imported Feeds'];
- } else {
- root = [this._folder];
- }
-
- this._feeds = [];
- this._tooDeep = false;
-
- this._getFeeds(root, body);
-
- if (this._tooDeep) {
- this._logger.error('Hierarchy at ' + url + ' is too deep');
- this._sendLoadError(Ci.flockIError.OPML_TOO_DEEP);
- return;
- }
-
- if (this._feeds.length == 0) {
- this._logger.error('No feeds in OPML at ' + url);
- this._sendLoadError(Ci.flockIError.OPML_NO_FEEDS);
- return;
- }
-
- this._logger.info(this._feeds.length + ' feeds found in ' + url);
- this._subscribeFeeds();
- },
-
- _getFeeds: function OR__getFeeds(hierarchy, node) {
- for each (var outline in node.outline) {
- var xmlUrl = outline.@xmlUrl;
- if (xmlUrl && xmlUrl.toString().length > 0 &&
- !outline.hasComplexContent()) {
- var feed = { hierarchy: hierarchy,
- url: xmlUrl.toString(),
- title: null
- };
-
- text = outline.@text;
- if (!text || text.toString().length == 0)
- text = outline.@title;
-
- if (text && text.toString().length > 0)
- feed.title = text.toString();
-
- this._logger.info('Found feed ' + feed.url);
- this._feeds.push(feed);
- } else {
- var title = outline.@title;
- if (!title || title.toString().length == 0)
- title = outline.@text;
-
- if (title && title.toString().length > 0) {
- var newHierarchy = hierarchy.concat(title.toString());
- if (newHierarchy.length > MAX_OPML_DEPTH) {
- this._tooDeep = true;
- return;
- }
-
- this._logger.info('Found folder ' + title.toString());
-
- if (outline.hasComplexContent()) {
- this._getFeeds(newHierarchy, outline);
- } else {
- var feed = { hierarchy: newHierarchy,
- url: null
- };
- this._feeds.push(feed);
- }
-
- if (this._tooDeep)
- return;
- }
- }
- }
- },
-
- _subscribeFeeds: function OR__subscribeFeeds(folder, node) {
- var fm = Cc['@flock.com/feed-manager;1']
- .getService(Ci.flockIFeedManager);
-
- var ios = Cc['@mozilla.org/network/io-service;1']
- .getService(Ci.nsIIOService);
-
- var subscriber = {
- _feeds: this._feeds,
-
- _listener: this._listener,
- _ctxt: this._ctxt,
- _logger: this._logger,
- _url: this._url,
-
- _index: -1,
-
- get _feed() { return this._feeds[this._index]; },
-
- createFolderHierarchy: function(hierarchy) {
- var destFolder = null;
- for each (var folder in hierarchy) {
- if (folder instanceof Ci.flockIFeedFolder) {
- destFolder = folder;
- } else {
- var newFolder;
- try {
- newFolder = destFolder.addFolder(folder);
- this._logger.info('Created folder: ' + folder);
- }
- catch (e) {
- newFolder = destFolder.getChildFolder(folder);
- }
- destFolder = newFolder;
- }
- }
- return destFolder;
- },
-
- onGetFeedComplete: function(feed) {
- if (this._listener)
- this._listener.onGetFeed(feed, this._ctxt);
-
- var destFolder = this.createFolderHierarchy(this._feed.hierarchy);
-
- this._feed.hierarchy.length = 0;
- this._feed.hierarchy.push(destFolder);
-
- this._logger.info('Subscribing ' + this._feed.url + ' to folder ' +
- destFolder.getTitle());
- destFolder.subscribeFeed(feed);
-
- if (this._listener)
- this._listener.onSubscribe(feed, this._ctxt,
- this._index, this._feeds.length);
-
- this.processNextFeed();
- },
- onError: function(error) {
- this._logger.warn('Error fetching ' + this._feed.url);
-
- if (this._listener)
- this._listener.onError(this._feed.url, this._ctxt, error,
- this._index, this._feeds.length);
-
- this.processNextFeed();
- },
-
- processNextFeed: function() {
- this._index++;
-
- while (this._index < this._feeds.length) {
- if (this._feed.url == null) {
- this.createFolderHierarchy(this._feed.hierarchy);
- this._index++;
- continue;
- }
-
- var url = null;
- try {
- url = ios.newURI(this._feed.url, null, null);
- }
- catch (e) { }
-
- if (url) {
- this._logger.info('Fetching feed ' + this._feed.url);
- fm.getFeed(url, this);
- return;
- }
-
- if (this._listener) {
- this._logger.warn('Bad feed URL: ' + this._feed.url);
- var error = Cc['@flock.com/error;1'].createInstance(Ci.flockIError);
- error.errorCode = error.OPML_BAD_FEED_URL;
- this._listener.onError(this._feed.url, this._ctxt, error,
- this._index, this._feeds.length);
- }
-
- this._index++;
- }
-
- this._logger.info('Finished OPML subscription of ' + this._url.spec);
-
- if (this._listener)
- this._listener.onFinish(this._ctxt);
- }
- }
-
- subscriber.processNextFeed();
- },
-
- QueryInterface: function OR_QueryInterface(iid) {
- if (iid.equals(Ci.nsIStreamListener) ||
- iid.equals(Ci.nsIRequestObserver)||
- iid.equals(Ci.nsISupports))
- return this;
- throw Cr.NS_ERROR_NO_INTERFACE;
- },
- }
-
-
- function OpmlService() {
- this._logger = Cc['@flock.com/logger;1'].createInstance(Ci.flockILogger);
- this._logger.init('opmlservice');
- this._logger.info('created');
- }
-
- OpmlService.prototype = {
- subscribe: function OPML_subscribe(url, folder, flatRoot, listener, ctxt) {
- this._logger.info('subscribing OPML from ' + url);
-
- var req = new OpmlRequest(url, folder, flatRoot, listener, ctxt, this._logger);
- req.get();
- },
-
- export: function OPML_export(folder, title, file) {
- var opml =
- <opml version="1.1">
- <head>
- <title>{title}</title>
- </head>
- <body/>
- </opml>;
-
- this._exportFolder(folder, opml.body);
-
- writeXML(opml, file);
- },
-
- _exportFolder: function OPML__exportFolder(folder, parentNode) {
- var children = folder.getChildren();
-
- while (children && children.hasMoreElements()) {
- var child = children.getNext();
- var node = <outline/>;
-
- if (child instanceof Ci.flockIFeedFolder) {
- node.@text = child.getTitle();
- this._exportFolder(child, node);
- } else {
- node.@type = 'rss';
- node.@text = child.getTitle();
- node.@title = child.getTitle();
- node.@xmlUrl = child.getURL().spec;
- }
-
- parentNode.outline += node;
- }
- },
-
- getInterfaces: function OPML_getInterfaces(countRef) {
- var interfaces = [Ci.flockIOpmlService, Ci.nsIClassInfo, Ci.nsISupports];
- countRef.value = interfaces.length;
- return interfaces;
- },
- getHelperForLanguage: function OPML_getHelperForLanguage(language) {
- return null;
- },
- contractID: OPML_CONTRACTID,
- classDescription: OPML_CLASSNAME,
- classID: OPML_CLASSID,
- implementationLanguage: Ci.nsIProgrammingLanguage.JAVASCRIPT,
- flags: Ci.nsIClassInfo.SINGLETON,
-
- QueryInterface: function OPML_QueryInterface(iid) {
- if (iid.equals(Ci.flockIOpmlService) ||
- iid.equals(Ci.nsIClassInfo) ||
- iid.equals(Ci.nsISupports))
- return this;
- throw Cr.NS_ERROR_NO_INTERFACE;
- }
- }
-
-
- function MarkReadListener() {
- }
-
- MarkReadListener.prototype = {
- onSubscribe: function MARK_READ_onSubscribe(feed, ctxt, progress, progressMax) {
- },
- onFinish: function MARK_READ_onFinish(ctxt) {
- },
- onLoadError: function MARK_READ_onLoadError(ctxt, error) {
- },
- onError: function MARK_READ_onError(url, ctxt, error, progress, progressMax) {
- },
- onGetFeed: function MARK_READ_onGetFeed(feed, ctxt) {
- feed.markRead();
- },
-
- getInterfaces: function MARK_READ_getInterfaces(countRef) {
- var interfaces = [Ci.flockIOpmlSubscribeListener, Ci.nsIClassInfo,
- Ci.nsISupports];
- countRef.value = interfaces.length;
- return interfaces;
- },
- getHelperForLanguage: function MARK_READ_getHelperForLanguage(language) {
- return null;
- },
- contractID: MARK_READ_CONTRACTID,
- classDescription: MARK_READ_CLASSNAME,
- classID: MARK_READ_CLASSID,
- implementationLanguage: Ci.nsIProgrammingLanguage.JAVASCRIPT,
- flags: Ci.nsIClassInfo.SINGLETON,
-
- QueryInterface: function MARK_READ_QueryInterface(iid) {
- if (iid.equals(Ci.flockIOpmlSubscribeListener) ||
- iid.equals(Ci.nsIClassInfo) ||
- iid.equals(Ci.nsISupports))
- return this;
- throw Cr.NS_ERROR_NO_INTERFACE;
- }
- }
-
-
- function GenericComponentFactory(ctor) {
- this._ctor = ctor;
- }
-
- GenericComponentFactory.prototype = {
-
- _ctor: null,
-
- // nsIFactory
- createInstance: function(outer, iid) {
- if (outer != null)
- throw Cr.NS_ERROR_NO_AGGREGATION;
- return (new this._ctor()).QueryInterface(iid);
- },
-
- // nsISupports
- QueryInterface: function(iid) {
- if (iid.equals(Ci.nsIFactory) ||
- iid.equals(Ci.nsISupports))
- return this;
- throw Cr.NS_ERROR_NO_INTERFACE;
- },
- };
-
- var Module = {
- QueryInterface: function(iid) {
- if (iid.equals(Ci.nsIModule) ||
- iid.equals(Ci.nsISupports))
- return this;
-
- throw Cr.NS_ERROR_NO_INTERFACE;
- },
-
- getClassObject: function(cm, cid, iid) {
- if (!iid.equals(Ci.nsIFactory))
- throw Cr.NS_ERROR_NOT_IMPLEMENTED;
-
- if (cid.equals(OPML_CLASSID))
- return new GenericComponentFactory(OpmlService)
- if (cid.equals(MARK_READ_CLASSID))
- return new GenericComponentFactory(MarkReadListener)
-
- throw Cr.NS_ERROR_NO_INTERFACE;
- },
-
- registerSelf: function(cm, file, location, type) {
- var cr = cm.QueryInterface(Ci.nsIComponentRegistrar);
- cr.registerFactoryLocation(OPML_CLASSID, OPML_CLASSNAME,
- OPML_CONTRACTID,
- file, location, type);
- cr.registerFactoryLocation(MARK_READ_CLASSID, MARK_READ_CLASSNAME,
- MARK_READ_CONTRACTID,
- file, location, type);
- },
-
- unregisterSelf: function(cm, location, type) {
- var cr = cm.QueryInterface(Ci.nsIComponentRegistrar);
- cr.unregisterFactoryLocation(OPML_CLASSID, location);
- cr.unregisterFactoryLocation(MARK_READ_CLASSID, location);
- },
-
- canUnload: function(cm) {
- return true;
- },
- };
-
- function NSGetModule(compMgr, fileSpec)
- {
- return Module;
- }
-